#include "PrecompiledHeaders.h"
#include "ChaseCameraControl.h"
#include "InputDevice.h"
#include "Ball.h"

ChaseCameraControl::ChaseCameraControl(Ogre::Viewport *m_v, Ogre::Viewport *l_v,
    Ogre::Viewport *r_v, Ogre::SceneManager *sm, Ogre::String prefix) :
        CameraControl(m_v, l_v, r_v, sm, prefix)
{
    yaw_speed = 0;
    MIN_SCALE = 0.2;
    initial_yaw = Ogre::Radian(0);
}

ChaseCameraControl::~ChaseCameraControl() {}

/*
 * Register a player whose ball we'll "chase". Create a child scene node of the
 * ball and attach all the camera to this
 */
void ChaseCameraControl::registerPlayer(Player *p, OgreNewt::World *og)
{
    if (player)
        throw new Ogre::Exception(1001, "Player already registered with this camera", "ChaseCameraControl::registerPlayer");
    ogre_newt = og;
    player = p;
    player->getInputDevice()->useCamera(this);
    main_node = player->getBall()->getSceneNode()->createChildSceneNode();
    main_node->setFixedYawAxis(true);
    main_node->setInitialState();
    main_node->setInheritOrientation(false);
    main_node->attachObject(main_camera);
    initial_direction = p->getBall()->getInitialDirection();
    initial_yaw = acos(Ogre::Vector3::NEGATIVE_UNIT_Z.dotProduct(initial_direction));
    main_node->yaw(initial_yaw);
    main_viewport->setCamera(main_camera);
    if(left_viewport)
    {
        main_node->attachObject(left_camera);
        left_viewport->setCamera(left_camera);
    }
    if(right_viewport)
    {
        main_node->attachObject(right_camera);
        right_viewport->setCamera(right_camera);
    }
    main_node->yaw(yaw);
    main_node->pitch(pitch);
    main_node->roll(roll);
    position_scale = previous_scale = 1.0;
    extra_pitch = Ogre::Radian(-1.0);
    zoom = (1 - Constants::ZOOM_FACTOR) + (Constants::ZOOM_FACTOR * player->getBall()->getScale());
}

/*
 * Pitch and roll the camera node based on the amount the world is pitched and
 * rolled. Yaw the camera node towards the ball's direction in a smooth way so
 * that we follow the ball without disorientating the player
 */
void ChaseCameraControl::updateCamera(Ogre::Real time)
{
    if (player == NULL)
        throw new Ogre::Exception(1001, "No player registered with this camera", "ChaseCameraControl::updateCamera");
        
    Ogre::Vector3 ball_velocity, gravity_direction;
    
    ball_velocity = player->getBall()->getVelocity();
    gravity_direction = player->getBall()->getGravityDirection();
    
    main_node->roll(-roll);
    main_node->pitch(-pitch - (extra_pitch * (1 - previous_scale)));
    
    Ogre::Quaternion rotation(-yaw - initial_yaw, Ogre::Vector3::UNIT_Y);
    rotation = Ogre::Vector3::NEGATIVE_UNIT_Y.getRotationTo(rotation * gravity_direction);
    
    pitch = rotation.getPitch(false);
    roll = rotation.getRoll(false);
    
    Ogre::Vector3 cam_direction = main_camera->getRealDirection();
    cam_direction.normalise();
    
    Ogre::Vector3 ball_direction = ball_velocity;
    float ball_speed = ball_direction.normalise();
    
    // yaw the cam node towards the ball's direction
    if(!player->isTeleporting() && ball_speed > 0.5)
    {
        // yaw needed is the amount we need to yaw by to look in the direction that the ball's moving
        Ogre::Radian yaw_needed = cam_direction.getRotationTo(ball_direction).getYaw(false);
        if(yaw_needed > Ogre::Radian(0.001) || yaw_needed < Ogre::Radian(-0.001))
        {
            // if we can't get there in 1 second, increase the yaw speed else decrease it
            if(yaw_speed * Constants::TRACKING_TIME < yaw_needed) yaw_speed += Constants::TRACKING_SPEED_CHANGE;
            else yaw_speed -= Constants::TRACKING_SPEED_CHANGE;
            yaw += yaw_speed * time;
            main_node->yaw(Ogre::Radian(yaw_speed * time));
        }
    }
    
    main_node->pitch(pitch + (extra_pitch * (1 - position_scale)));
    main_node->roll(roll);
    
    if(!player->isTeleporting()) updateScale(time);
    zoom = (1 - Constants::ZOOM_FACTOR) + (Constants::ZOOM_FACTOR * player->getBall()->getScale());
    
    main_camera->setPosition((position * (MIN_SCALE + position_scale) * zoom));
    if(left_viewport) left_camera->setPosition((position * (MIN_SCALE + position_scale) * zoom) + l_offset);
    if(right_viewport) right_camera->setPosition((position * (MIN_SCALE + position_scale) * zoom) - l_offset);
    
    if(pod && player->isUsingPod()) pod->setRollAndPitch(roll.valueRadians(), pitch.valueRadians());
}

/*
 * Called when player is reset so that camera is in the same position and the
 * looking in the same direction as when the game started
 */
void ChaseCameraControl::reset()
{
    main_node->resetOrientation();
    main_node->yaw(initial_yaw);
    pitch = roll = yaw = Ogre::Radian(0);
    yaw_speed = Ogre::Radian(0);
    position_scale = 1.0;
    zoom = 1.0;
}
